package resolution;

import programme.Programme;
import manager.Donnees;
import manager.Solution;
import ilog.concert.IloException;
import ilog.concert.IloIntExpr;
import ilog.concert.IloIntVar;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

public class CPlex {
	/**
	 * Référence vers le programme à optimiser, permettant d'accéder à toutes les données nécessaires.
	 */
	private static Programme programme;
	
	public static Solution rechercherBorneDuale(Programme prog) {
		try  {
			CPlex.programme = prog;
			Donnees data = CPlex.programme.getDonnees();
			
			IloCplex  cplex  =  new  IloCplex();
			
			//Nombre de variables
			IloNumVar[][][][] y = new IloNumVar[data.taille][data.taille][data.taille][data.taille];
			IloNumVar[][] x = new IloNumVar[data.taille][data.taille];
			
			//Coefficients devant les variables
			IloNumExpr[] prods = new IloNumExpr[(int)Math.pow(data.taille, 4)];
			
			for(int i = 0; i < data.taille; ++i) {
				for(int j = 0; j < data.taille; ++j) {
					for(int k = 0; k < data.taille; ++k) {
						for(int l = 0; l < data.taille; ++l) {
							// Initialisation des variables y
							y[i][j][k][l] = cplex.numVar(0.0, 1.0);
							// Initialisation des coefficients devant les variables
							prods[l + data.taille*(k + data.taille*(j + data.taille*i))] = cplex.prod(data.tabDist[k][l] * data.tabFlux[i][j], y[i][j][k][l]);
						}
						//cplex.addMinimize(cplex.scalProd(y[i][j][k], coefs[i][j][k]));
					}
					// Initialisation des variables x
					x[i][j] = cplex.numVar(0.0, 1.0);
				}
			}
			
			// Fonction objectif
			cplex.addMinimize(cplex.sum(prods));
			
			
			for(int i = 0; i < data.taille; ++i) {
				for(int j = 0; j < data.taille; ++j) {
					for(int k = 0; k < data.taille; ++k) {
						for(int l = 0; l < data.taille; ++l) {
							// y[ijkl] <= x[ik] traduit ici par y[ijkl] - x[ik] <= 0
							cplex.addLe(cplex.sum(cplex.prod(1, y[i][j][k][l]),
									cplex.prod(-1.0, x[i][k])), 0.0);
							// ET
							// y[ijkl] <= x[jl] traduit ici par y[ijkl] - x[jl] <= 0
							cplex.addLe(cplex.sum(cplex.prod(1.0, y[i][j][k][l]),
									cplex.prod(-1.0, x[j][l])), 0.0);
							// ET
							// y[ijkl] >= x[ik] + x[jl] - 1 traduit ici par y[ijkl] - x[ik] - x[jl] >= - 1
							cplex.addGe(cplex.sum(cplex.prod(1.0, y[i][j][k][l]),
									cplex.prod(-1.0, x[i][k]),
									cplex.prod(-1.0, x[j][l])), -1.0);
						}
					}
				}
			}
			

			// Sum x[ik] = 1 pour tout k
			/***** Méthode d'Adams *****/
			// transformé en Sum y[ijkl] = x[jl] pour tout j,k,l traduit en Sum y[ijkl] - x[jl] = 0
			for(int k = 0; k < data.taille; ++k) {
				IloNumExpr[] termes = new IloNumExpr[data.taille];
				for(int i = 0; i < data.taille; ++i) {
					termes[i] = cplex.prod(1, x[i][k]);
				}
				
				cplex.addEq(cplex.sum(termes), 1);
			}
			for(int l = 0; l < data.taille; ++l) {
				for(int k = 0; k < data.taille; ++k) {
					for(int j = 0; j < data.taille; ++j) {
						IloNumExpr[] termes = new IloNumExpr[data.taille + 1];
						for(int i = 0; i < data.taille; ++i) {
							termes[i] = cplex.prod(1.0, y[i][j][k][l]);
						}
						termes[data.taille] = cplex.prod(-1.0, x[j][l]);
						
						cplex.addEq(cplex.sum(termes), 0.0);
					}
				}
			}
			
			
			// Sum x[ik] = 1 pour tout i
			for(int i = 0; i < data.taille; ++i) {
				IloNumExpr[] termes = new IloNumExpr[data.taille];
				for(int k = 0; k < data.taille; ++k) {
					termes[k] = cplex.prod(1, x[i][k]);
				}
				
				cplex.addEq(cplex.sum(termes), 1);
			}
			for(int l = 0; l < data.taille; ++l) {
				for(int k = 0; k < data.taille; ++k) {
					for(int j = 0; j < data.taille; ++j) {
						IloNumExpr[] termes = new IloNumExpr[data.taille + 1];
						for(int i = 0; i < data.taille; ++i) {
							termes[i] = cplex.prod(1.0, y[k][j][i][l]);
						}
						termes[data.taille] = cplex.prod(-1.0, x[j][l]);
						
						cplex.addEq(cplex.sum(termes), 0.0);
					}
				}
			}
			
			/*for(int i = 0; i < data.taille; ++i) {
				for(int j = 0; j < data.taille; ++j) {
					for(int k = 0; k < data.taille; ++k) {
						for(int l = 0; l < data.taille; ++l) {
							cplex.addEq(cplex.sum(cplex.prod(1.0, y[i][j][k][l]), cplex.prod(-1.0, y[j][i][l][k])), 0.0);
						}
					}
				}
			}*/
			
			
			//Résolution du problème
			if(cplex.solve()) {
				cplex.output().println("Solution status = " + cplex.getStatus());
				cplex.output().println("Solution value = " + cplex.getObjValue());
				
				System.out.println("Résultat: " + cplex.getBestObjValue());
				for(int i = 0; i < data.taille; ++i) {
					for(int j = 0; j < data.taille; ++j) {
						for(int k = 0; k < data.taille; ++k) {
							for(int l = 0; l < data.taille; ++l) {
								if(false)
									cplex.output().println("Position: (" + i + ", " + j + ", " + k + ", " + l + ")\tValue = " + cplex.getValue(y[i][j][k][l]));
							}
						}
					}
				}
			}
			
			cplex.end();
		}
		catch (IloException  e) {
			System.err.println("Concert  exception  '"  +  e  +  "'  caught");
			return null;
		}
		return null;
	}
}
